﻿// -----------------------------------------------------------------------
// <copyright file="ConnectionManager.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
// -----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using GateShift.Servers.GameServer.LoggingSystem;
using GateShift.Servers.GameServer.Networking;
using GateShift.Servers.GameServer.Networking.Events;
using GateShift.Services.SDK.Services.HostIntegration;
using GateShift.Services.SDK.Threading;

#endregion

namespace GateShift.Servers.GameServer.Managers.Connection
{
    /// <summary>
    /// Management Class for all connections
    /// </summary>
    internal class ConnectionManager : IConnectionManager, ICommandLineInterfaceHandler
    {
        #region Variables

        private readonly object _wcfFactoryLock;
        private readonly List<Guid> _connections;
        private readonly ThreadedDictionary<string, ServiceHostEx> _wcfHosts;
        private readonly ThreadedDictionary<Type, ChannelFactory> _wcfClients;
        private Thread _connectionManagerThread;
        private ServerNetwork _clientNet;

        #endregion

        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the ConnectionManager class
        /// </summary>
        public ConnectionManager()
        {
            _connections = new List<Guid>();
            _wcfFactoryLock = new object();
            _wcfHosts = new ThreadedDictionary<string, ServiceHostEx>();
            _wcfClients = new ThreadedDictionary<Type, ChannelFactory>();
        }

        #endregion

        #region Events

        /// <summary>
        /// Event for handling clients after they have connected
        /// </summary>
        public event EventHandler<ClientEventArgs> ClientConnected;

        /// <summary>
        /// Event for handling clients after they have disconnected
        /// </summary>
        public event EventHandler<ClientEventArgs> ClientDisconnected;

        /// <summary>
        /// Event for handling clients after a packet has been released
        /// </summary>
        public event EventHandler<ClientPacketEventArgs> ClientPacketRecieved;

        #endregion

        /// <summary>
        /// Gets a value indicating whether or not the manager status shows it is running
        /// </summary>
        public bool IsAlive
        {
            get { return _connectionManagerThread != null && _connectionManagerThread.IsAlive; }
        }

        #region IConnectionManager Members

        /// <summary>
        /// Sends data to the specified client by ID
        /// </summary>
        /// <param name="clientID">Client ID to send data to</param>
        /// <param name="packet">Packet to send to the client</param>
        public void SendData(Guid clientID, ClientPacket packet)
        {
            if (packet.PacketData != null)
            {
                _clientNet.SendData(clientID, packet.ServiceID, packet.ServiceCode, packet.PacketData);
            }
        }

        /// <summary>
        /// Disconnects the specified client
        /// </summary>
        /// <param name="clientID">Client ID to disconnect</param>
        public void Disconnect(Guid clientID)
        {
            _clientNet.Disconnect(clientID);
        }

        /// <summary>
        /// Disconnects all clients
        /// </summary>
        public void Disconnect()
        {
            foreach (Guid connection in _connections)
            {
                Disconnect(connection);
            }
        }

        /// <summary>
        /// Checks to see if a client is currently connected
        /// </summary>
        /// <param name="clientID">Client ID to check</param>
        /// <returns>Whether or not the client is currently connected</returns>
        public bool IsConnected(Guid clientID)
        {
            return _connections.Contains(clientID);
        }

        #endregion

        #region IManager Members

        /// <summary>
        /// Starts the manager background thread
        /// </summary>
        public void Start()
        {
            if (IsAlive)
            {
                throw new InvalidOperationException("Stop must be called before restarting");
            }

            _connectionManagerThread = new Thread(ConnectionManagerThread)
                                           {
                                               Name = "Connection Manager",
                                               Priority = ThreadPriority.BelowNormal
                                           };

            Log.AddEntry("Starting Manager", "ConnectionManager", LogPriorities.Informative, TraceEventType.Start, (int)CoreLogCategories.ServiceManager);

            _connectionManagerThread.Start();
        }

        /// <summary>
        /// Stops the manager background thread
        /// </summary>
        public void Stop()
        {
            if (IsAlive)
            {
                Log.AddEntry("Stopping Manager", "ConnectionManager", LogPriorities.Informative, TraceEventType.Stop, (int)CoreLogCategories.ServiceManager);
                _connectionManagerThread.Abort();
                _connectionManagerThread.Join(10000);
            }

            _connectionManagerThread = null;
        }

        /// <summary>
        /// Initializes all manager resources
        /// </summary>
        public void Initialize()
        {
            Log.AddEntry("Initializing Manager", "ConnectionManager", LogPriorities.Informative, TraceEventType.Verbose, (int)CoreLogCategories.ServiceManager);
        }

        #endregion

        #region ICommandLineInterfaceHandler Members

        /// <summary>
        /// Entry method to process a set of commands for the root command
        /// </summary>
        /// <param name="arguments">List of string with all the arguments for this command</param>
        /// <returns>bool whether or not the command could be processed</returns>
        public bool HandleCommand(List<string> arguments)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }

            // If there is nothing to process let the requestor know
            if (arguments.Count < 1)
            {
                return false;
            }

            List<string> subArguments = (arguments.Count > 1) ? arguments.GetRange(1, arguments.Count - 1) : new List<string>();

            switch (arguments[0])
            {
                case "local":
                    return HandleServiceHostCommands(subArguments);
            }

            return false;
        }

        #endregion

        /// <summary>
        /// Adds the specified object to the managed ServiceHost System
        /// </summary>
        /// <param name="key">Key used to identity the service</param>
        /// <param name="hostItem">object that contains the Contract</param>
        /// <exception cref="ArgumentNullException">Thrown if key or hostItem are null</exception>
        /// <exception cref="InvalidOperationException">Thrown if the key already exists</exception>
        public void AddServiceHost(string key, object hostItem)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (hostItem == null)
            {
                throw new ArgumentNullException("hostItem");
            }

            if (_wcfHosts.ContainsKey(key))
            {
                throw new InvalidOperationException("Service Host key already exists");
            }

            _wcfHosts.Add(key, new ServiceHostEx(hostItem, key));

            OpenServiceHost(key);
        }

        /// <summary>
        /// Removes the specified key from the managed ServiceHost System
        /// </summary>
        /// <param name="key">Key used to identity the service</param>
        /// <exception cref="ArgumentNullException">Thrown if key is null</exception>
        /// <exception cref="KeyNotFoundException">Thrown if the key does not exist</exception>
        public void RemoveServiceHost(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (!_wcfHosts.ContainsKey(key))
            {
                throw new KeyNotFoundException(string.Format("Could not find ServiceHost by the key specified: {0}", key));
            }

            CloseServiceHost(key);

            _wcfHosts.Remove(key);
        }

        /// <summary>
        /// Opens a service channel for communicating of <typeparamref name="T" />
        /// </summary>
        /// <typeparam name="T">Service Interface to create the channel for</typeparam>
        /// <returns>Service channel created of <typeparamref name="T" /></returns>
        public T OpenServiceChannel<T>()
        {
            ChannelFactory<T> factory = null;

            // We lock so we make sure only 1 factory gets opened or modified
            lock (_wcfFactoryLock)
            {
                // Lets find the existing one
                if (_wcfClients.ContainsKey(typeof(T)))
                {
                    factory = (ChannelFactory<T>)_wcfClients[typeof(T)];

                    if (factory.State != CommunicationState.Opened)
                    {
                        factory.Closed -= ChannelFactory_Closed;
                        factory.Faulted -= ChannelFactory_Faulted;
                        factory.Opened -= ChannelFactory_Opened;
                        _wcfClients.RemoveSafe(typeof(T));
                        factory = null;
                    }
                }

                // If the factory is bad or does not exist, create the new one
                if (factory == null)
                {
                    factory = new ChannelFactory<T>();
                    factory.Closed += ChannelFactory_Closed;
                    factory.Faulted += ChannelFactory_Faulted;
                    factory.Opened += ChannelFactory_Opened;
                    factory.Open();
                    _wcfClients.Add(typeof(T), factory);
                }
            }

            T channel = factory.CreateChannel();
            ((IChannel)channel).Closed += Channel_Closed;
            ((IChannel)channel).Opened += Channel_Opened;
            ((IChannel)channel).Faulted += Channel_Faulted;
            return channel;
        }

        /// <summary>
        /// Closes a service channel of <typeparamref name="T" /> that was previously opened
        /// </summary>
        /// <param name="channel">A <typeparamref name="T" /> based channel to close</param>
        /// <typeparam name="T">Service Interface to create the channel for</typeparam>
        public void CloseServiceChannel<T>(T channel)
        {
            IChannel client = channel as IChannel;

            if (client == null || client.State != CommunicationState.Opened)
            {
                return;
            }

            client.Closed -= Channel_Closed;
            client.Opened -= Channel_Opened;
            client.Faulted -= Channel_Faulted;
            client.Close();
        }

        #region EventRaisers

        /// <summary>
        /// Event Raiser for ClientConnected
        /// </summary>
        /// <param name="e">ClientEventArgs to pass on to the event</param>
        protected void OnClientConnected(ClientEventArgs e)
        {
            if (ClientConnected != null)
            {
                ClientConnected(this, e);
            }
        }

        /// <summary>
        /// Event Raiser for ClientDisconnected
        /// </summary>
        /// <param name="e">ClientEventArgs to pass on to the event</param>
        protected void OnClientDisconnected(ClientEventArgs e)
        {
            if (ClientDisconnected != null)
            {
                ClientDisconnected(this, e);
            }
        }

        /// <summary>
        /// Event Raiser for ClientPacketRecieved
        /// </summary>
        /// <param name="e">ClientPacketEventArgs to pass on to the event</param>
        protected void OnClientPacketRecieved(ClientPacketEventArgs e)
        {
            if (ClientPacketRecieved != null)
            {
                ClientPacketRecieved(this, e);
            }
        }

        #endregion

        #region WCF Proxy Events

        /// <summary>
        /// Event Handler for a ChannelFactory being Opened
        /// </summary>
        /// <param name="sender">ChannelFactory calling this event</param>
        /// <param name="e">Empty EventArgs</param>
        private static void ChannelFactory_Opened(object sender, EventArgs e)
        {
            ChannelFactory factory = sender as ChannelFactory;

            if (factory == null)
            {
                throw new ArgumentException("Sender must be of type ChannelFactory", "sender");
            }

            Log.AddEntry("Factory Opened", String.Format("Opening {0}", factory.GetType()), LogPriorities.Informative, TraceEventType.Verbose, (int)CoreLogCategories.ServiceManager);
        }

        /// <summary>
        /// Event Handler for a ChannelFactory being Faulted
        /// </summary>
        /// <param name="sender">ChannelFactory calling this event</param>
        /// <param name="e">Empty EventArgs</param>
        private static void ChannelFactory_Faulted(object sender, EventArgs e)
        {
            ChannelFactory factory = sender as ChannelFactory;

            if (factory == null)
            {
                throw new ArgumentException("Sender must be of type ChannelFactory", "sender");
            }

            Log.AddEntry("Factory Faulted", String.Format("{0} Faulted", factory.GetType()), LogPriorities.Warning, TraceEventType.Warning, (int)CoreLogCategories.ServiceManager);
        }

        /// <summary>
        /// Event Handler for a ChannelFactory being Closed
        /// </summary>
        /// <param name="sender">ChannelFactory calling this event</param>
        /// <param name="e">Empty EventArgs</param>
        private static void ChannelFactory_Closed(object sender, EventArgs e)
        {
            ChannelFactory factory = sender as ChannelFactory;

            if (factory == null)
            {
                throw new ArgumentException("Sender must be of type ChannelFactory", "sender");
            }

            Log.AddEntry("Factory Closed", String.Format("{0} Closed", factory.GetType()), LogPriorities.Informative, TraceEventType.Verbose, (int)CoreLogCategories.ServiceManager);
        }

        /// <summary>
        /// Event Handler for a Channel being Opened
        /// </summary>
        /// <param name="sender">ChannelFactory calling this event</param>
        /// <param name="e">Empty EventArgs</param>
        private static void Channel_Opened(object sender, EventArgs e)
        {
            IChannel channel = sender as IChannel;

            if (channel == null)
            {
                throw new ArgumentException("Sender must be of type IChannel", "sender");
            }

            Log.AddEntry("Channel Opened", String.Format("Opening {0}", channel.GetType()), LogPriorities.Informative, TraceEventType.Verbose, (int)CoreLogCategories.ServiceManager);
        }

        /// <summary>
        /// Event Handler for a Channel being Faulted
        /// </summary>
        /// <param name="sender">ChannelFactory calling this event</param>
        /// <param name="e">Empty EventArgs</param>
        private static void Channel_Faulted(object sender, EventArgs e)
        {
            IChannel channel = sender as IChannel;

            if (channel == null)
            {
                throw new ArgumentException("Sender must be of type IChannel", "sender");
            }

            Log.AddEntry("Channel Faulted", String.Format("{0} Faulted", channel.GetType()), LogPriorities.Warning, TraceEventType.Warning, (int)CoreLogCategories.ServiceManager);
        }

        /// <summary>
        /// Event Handler for a Channel being Closed
        /// </summary>
        /// <param name="sender">ChannelFactory calling this event</param>
        /// <param name="e">Empty EventArgs</param>
        private static void Channel_Closed(object sender, EventArgs e)
        {
            IChannel channel = sender as IChannel;

            if (channel == null)
            {
                throw new ArgumentException("Sender must be of type IChannel", "sender");
            }

            Log.AddEntry("Channel Closed", String.Format("{0} Closed", channel.GetType()), LogPriorities.Informative, TraceEventType.Verbose, (int)CoreLogCategories.ServiceManager);
        }

        #endregion

        #region Service Host Events

        /// <summary>
        /// Event handler for the ServiceHost getting an unknown message packet
        /// </summary>
        /// <param name="sender">ServiceHostEx calling this method</param>
        /// <param name="e">UnknownMessageReceivedEventArgs with information about the event</param>
        /// <exception cref="ArgumentException">Thrown if the sender is not of type ServiceHostEx</exception>
        private static void ServiceHost_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e)
        {
            ServiceHostEx host = sender as ServiceHostEx;
            if (host == null)
            {
                throw new ArgumentException("Sender must be of ServiceHostEx type", "sender");
            }

            Log.AddEntry("ServiceHost Warning", string.Format("ServiceHost {0} has recieved an invalid message: {1}.", host.Name, e.Message), LogPriorities.Informative, TraceEventType.Information, (int)CoreLogCategories.ConnectionManager);
        }

        /// <summary>
        /// Event handler for the ServiceHost opening
        /// </summary>
        /// <param name="sender">ServiceHostEx calling this method</param>
        /// <param name="e">Empty EventArgs</param>
        /// <exception cref="ArgumentException">Thrown if the sender is not of type ServiceHostEx</exception>
        private static void ServiceHost_Opened(object sender, EventArgs e)
        {
            ServiceHostEx host = sender as ServiceHostEx;
            if (host == null)
            {
                throw new ArgumentException("Sender must be of ServiceHostEx type", "sender");
            }

            Log.AddEntry("ServiceHost Open", string.Format("ServiceHost {0} has gone into a open state.", host.Name), LogPriorities.Informative, TraceEventType.Information, (int)CoreLogCategories.ConnectionManager);
        }

        /// <summary>
        /// Event handler for the ServiceHost faulting
        /// </summary>
        /// <param name="sender">ServiceHostEx calling this method</param>
        /// <param name="e">Empty EventArgs</param>
        /// <exception cref="ArgumentException">Thrown if the sender is not of type ServiceHostEx</exception>
        private static void ServiceHost_Faulted(object sender, EventArgs e)
        {
            ServiceHostEx host = sender as ServiceHostEx;
            if (host == null)
            {
                throw new ArgumentException("Sender must be of ServiceHostEx type", "sender");
            }

            Log.AddEntry("ServiceHost Faulted", string.Format("ServiceHost {0} has gone into a faulted state.", host.Name), LogPriorities.Informative, TraceEventType.Information, (int)CoreLogCategories.ConnectionManager);
        }

        /// <summary>
        /// Event handler for the ServiceHost closing
        /// </summary>
        /// <param name="sender">ServiceHostEx calling this method</param>
        /// <param name="e">Empty EventArgs</param>
        /// <exception cref="ArgumentException">Thrown if the sender is not of type ServiceHostEx</exception>
        private static void ServiceHost_Closed(object sender, EventArgs e)
        {
            ServiceHostEx host = sender as ServiceHostEx;
            if (host == null)
            {
                throw new ArgumentException("Sender must be of ServiceHostEx type", "sender");
            }

            Log.AddEntry("ServiceHost Closed", string.Format("ServiceHost {0} has gone into a closed state.", host.Name), LogPriorities.Informative, TraceEventType.Information, (int)CoreLogCategories.ConnectionManager);
        }

        #endregion

        #region SubCommand Handlers

        /// <summary>
        /// Handler for all connection local ... commands
        /// </summary>
        /// <param name="arguments">List of arguments to handle</param>
        /// <returns>bool based on whether or not the command was successfully handled</returns>
        private bool HandleServiceHostCommands(IList<string> arguments)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException("arguments");
            }

            // If there is nothing to process let the requestor know
            if (arguments.Count < 1)
            {
                return false;
            }

            // Switch based on the sub config command
            switch (arguments[0].ToLower())
            {
                case "close":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    try
                    {
                        CloseServiceHost(arguments[1]);
                    }
                    catch (KeyNotFoundException)
                    {
                        return false;
                    }

                    break;

                case "open":
                    if (arguments.Count < 2)
                    {
                        return false;
                    }

                    try
                    {
                        OpenServiceHost(arguments[1]);
                    }
                    catch (KeyNotFoundException)
                    {
                        return false;
                    }

                    break;

                default:
                    return false;
            }

            return true;
        }

        #endregion

        #region Manager Functions

        /// <summary>
        /// Main Background Management Thread Method
        /// </summary>
        private void ConnectionManagerThread()
        {
            // Catch thread aborting
            try
            {
                Log.AddEntry("Thread Started", "Connection Manager Thread", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);

                byte[] encryptKey = Convert.FromBase64String(Properties.Settings.Default.EncryptionKey);
                byte[] encryptIV = Convert.FromBase64String(Properties.Settings.Default.EncryptionIV);
                string encryptName = Properties.Settings.Default.EncryptionName;
                IPAddress bindIP;

                if (!IPAddress.TryParse(Properties.Settings.Default.ListenAddress, out bindIP))
                {
                    bindIP = IPAddress.Any;
                }

                _clientNet = new ServerNetwork(bindIP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6);

                _clientNet.SetEncryption(encryptName, encryptKey, encryptIV);

                _clientNet.Bind(new IPEndPoint(bindIP, Properties.Settings.Default.ListenPort));
                _clientNet.MaximumTotalConnections = Properties.Settings.Default.MaximumServerConnections;
                _clientNet.CompressOutboundTraffic = Properties.Settings.Default.CompressOutboundTraffic;
                _clientNet.MaximumWaitingConnections = Properties.Settings.Default.MaximumQueuedConnections;
                _clientNet.StartAccepting();

                _clientNet.DataRecieved += ClientNet_DataRecieved;
                _clientNet.ClientConnected += ClientNet_ClientConnected;
                _clientNet.ClientDisconnected += ClientNet_ClientDisconnected;

                Thread.Sleep(Timeout.Infinite);
            }
            catch (ThreadAbortException)
            {
                Log.AddEntry("Thread Stopping", "Connection Manager Thread", LogPriorities.Verbose, TraceEventType.Verbose, (int)CoreLogCategories.ConsoleManager);

                _clientNet.DataRecieved -= ClientNet_DataRecieved;
                _clientNet.ClientConnected -= ClientNet_ClientConnected;
                _clientNet.ClientDisconnected -= ClientNet_ClientDisconnected;

                foreach (ChannelFactory item in _wcfClients.Values)
                {
                    CloseServiceChannel(item);
                }
            }
        }

        #endregion

        #region ServerNetwork Events

        /// <summary>
        /// Event handler for a connection being created
        /// </summary>
        /// <param name="sender">ServerNetwork calling this method</param>
        /// <param name="e">ConnectionIDEventArgs with information about the event</param>
        private void ClientNet_ClientConnected(object sender, ConnectionIDEventArgs e)
        {
            _connections.Add(e.ConnectionID);
            OnClientConnected(new ClientEventArgs(e.ConnectionID));
        }

        /// <summary>
        /// Event handler for a disconnection being made
        /// </summary>
        /// <param name="sender">ServerNetwork calling this method</param>
        /// <param name="e">ConnectionIDEventArgs with information about the event</param>
        private void ClientNet_ClientDisconnected(object sender, ConnectionIDEventArgs e)
        {
            _connections.Remove(e.ConnectionID);
            OnClientDisconnected(new ClientEventArgs(e.ConnectionID));
        }

        /// <summary>
        /// Event handler for a packet being recieved
        /// </summary>
        /// <param name="sender">ServerNetwork calling this method</param>
        /// <param name="e">ServerDataRecievedEventArgs with information about the event</param>
        private void ClientNet_DataRecieved(object sender, ServerDataRecievedEventArgs e)
        {
            ClientPacket packet = new ClientPacket
                                      {
                                          ServiceID = e.PrimaryCode,
                                          ServiceCode = e.SecondaryCode,
                                          PacketData = e.Data,
                                          WasEncrypted = e.WasEncrypted
                                      };

            OnClientPacketRecieved(new ClientPacketEventArgs(e.ConnectionID, packet));
        }

        #endregion

        #region Service Host Methods

        /// <summary>
        /// Closes the specified service from the managed ServiceHost System
        /// </summary>
        /// <param name="key">Key used to identity the service</param>
        /// <exception cref="ArgumentNullException">Thrown if key is null</exception>
        /// <exception cref="KeyNotFoundException">Thrown if the key does not exist</exception>
        private void CloseServiceHost(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (!_wcfHosts.ContainsKey(key))
            {
                throw new KeyNotFoundException(string.Format("Could not find ServiceHost by the key specified: {0}", key));
            }

            if (_wcfHosts[key].State == CommunicationState.Closed || _wcfHosts[key].State == CommunicationState.Closing)
            {
                return;
            }

            if (_wcfHosts[key].State == CommunicationState.Faulted)
            {
                Log.AddEntry("ServiceHost Close Failed", string.Format("ServiceHost {0} cannot be closed because it is faulted", _wcfHosts[key].Name), LogPriorities.Informative, TraceEventType.Information, (int)CoreLogCategories.ConnectionManager);
                return;
            }

            try
            {
                _wcfHosts[key].Close(new TimeSpan(30000));
            }
            catch (TimeoutException)
            {
                Log.AddEntry("ServiceHost Close Failed", string.Format("ServiceHost {0} cannot be closed because the close timed out", _wcfHosts[key].Name), LogPriorities.Informative, TraceEventType.Information, (int)CoreLogCategories.ConnectionManager);
            }

            _wcfHosts[key].Closed -= ServiceHost_Closed;
            _wcfHosts[key].Faulted -= ServiceHost_Faulted;
            _wcfHosts[key].Opened -= ServiceHost_Opened;
            _wcfHosts[key].UnknownMessageReceived -= ServiceHost_UnknownMessageReceived;
        }

        /// <summary>
        /// Opens the specified service in the managed ServiceHost System
        /// </summary>
        /// <param name="key">Key used to identity the service</param>
        /// <exception cref="ArgumentNullException">Thrown if key is null</exception>
        /// <exception cref="KeyNotFoundException">Thrown if the key does not exist</exception>
        private void OpenServiceHost(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (!_wcfHosts.ContainsKey(key))
            {
                throw new KeyNotFoundException(string.Format("Could not find ServiceHost by the key specified: {0}", key));
            }

            if (_wcfHosts[key].State == CommunicationState.Opened || _wcfHosts[key].State == CommunicationState.Opening)
            {
                return;
            }

            if (_wcfHosts[key].State != CommunicationState.Created)
            {
                _wcfHosts[key] = new ServiceHostEx(_wcfHosts[key].SingletonInstance, _wcfHosts[key].Name);
            }

            if (_wcfHosts[key].State == CommunicationState.Created)
            {
                _wcfHosts[key].Closed += ServiceHost_Closed;
                _wcfHosts[key].Faulted += ServiceHost_Faulted;
                _wcfHosts[key].Opened += ServiceHost_Opened;
                _wcfHosts[key].UnknownMessageReceived += ServiceHost_UnknownMessageReceived;
            }

            if (_wcfHosts[key].State == CommunicationState.Faulted)
            {
                Log.AddEntry("ServiceHost Open Failed", string.Format("ServiceHost {0} cannot be opened because it is faulted", _wcfHosts[key].Name), LogPriorities.Informative, TraceEventType.Information, (int)CoreLogCategories.ConnectionManager);
                return;
            }

            try
            {
                _wcfHosts[key].Open(new TimeSpan(30000));
            }
            catch (TimeoutException)
            {
                Log.AddEntry("ServiceHost Open Failed", string.Format("ServiceHost {0} cannot be opened because the open timed out", _wcfHosts[key].Name), LogPriorities.Warning, TraceEventType.Warning, (int)CoreLogCategories.ConnectionManager);
            }
            catch (InvalidOperationException ex)
            {
                Log.AddEntry("ServiceHost Open Failed", string.Format("ServiceHost {0} cannot be opened because the open timed out: {1}", _wcfHosts[key].Name, ex), LogPriorities.Warning, TraceEventType.Warning, (int)CoreLogCategories.ConnectionManager);
            }
        }

        #endregion

        #region Private Classes

        /// <summary>
        /// Inheritance class to attach identification properties to ServiceHost
        /// </summary>
        private class ServiceHostEx : ServiceHost
        {
            /// <summary>
            /// Initializes a new instance of the ServiceHostEx class
            /// </summary>
            /// <param name="singletonInstance">The instance of the hosted service</param>
            /// <param name="name">Name to identify this instance</param>
            /// <param name="baseAddresses">An array of the type Uri that contains the base addresses for the hosted instance</param>
            public ServiceHostEx(object singletonInstance, string name, params Uri[] baseAddresses)
                : base(singletonInstance, baseAddresses)
            {
                Name = name;
            }

            /// <summary>
            /// Gets or sets the name to identify this instance
            /// </summary>
            public string Name { get; private set; }
        }

        #endregion
    }
}